package org.apcs.pokemon.io.net;

/**
 * Authors:	        Jimmy Blanchard, Zachary Reed
 * Date Created:    Mar 26, 2010
 * Last Modified:   Apr 14, 2010
 * Package:         org.apcs.pokemon.io.net
 * Filename:        Client.java
 * Description:     A class that creates a common interface for communication
 *                  between players & handles connections and data transfer.
 */

/**
 * TODO: Decide who processes game data (host client,remote client, or both)
 */

import java.net.DatagramPacket;
import java.net.InetAddress;
import java.net.MulticastSocket;
import java.util.ArrayList;
import java.util.Timer;

public class Client {

    private static String strName = "";
    private static int intStatus = 2;
    private static ArrayList<Player> arlPlayers = new ArrayList<Player>();
    private UDPListenThread udpListen = new UDPListenThread(54387);

    /**
     * Creates a client to communicate with other players
     *
     * Valid statuses(?):
     *      0   =   In battle (unavailable)
     *      1   =   Wants to battle (available)
     *      2   =   Doesn't want to battle (unavailabe)
     *
     * @param: name Player name
     * @param: status Player status
     * @param: port Port to listen for data on (both TCP & UDP)
     */
    public Client(String playerName){
        strName = playerName;

        /* Create a new UDP socket and listen on port 54387 */

        udpListen.start();
        //new AnnounceThread();
    }

    /**
     * Add a player to the list of known players.
     *
     * @param name Player name
     * @param status Player status
     * @param netinfo Player IP address & port number
     */
    private void addPlayer(String name,int status,InetAddress addr,int port) {
        arlPlayers.add(new Player(name,status,addr,port));
    }

    /**
     * Update a known player's status.
     *
     * @param player Player to be updated
     * @param status New player status
     */
    private void updatePlayerStatus (Player p, int newstatus) {
        arlPlayers.get(arlPlayers.indexOf(p)).setStatus(newstatus);
    }

    /**
     * Process data recieved from a player over a TCP network connection.
     */
    private void receiveData() {}

     /**
     * Send data to a player over a TCP network connection.
     */
    public void sendData() {}

    /**
     * Process data received from a player over a UDP network connection.
     *
     * @param data Byte[] of data
     * @param addrSource IP Address of sender
     * @param portSource Port of sender
     */
    private void receiveDataUDP(byte[] data,InetAddress source,int port) {
        String sdata = new String(data);
        int intStartIndex,intEndIndex = 0;

        /* Data type */
        intStartIndex = 1;
        intEndIndex = sdata.indexOf(">");
        String strDataType = sdata.substring(intStartIndex,intEndIndex);
        
        /* Command */
        intStartIndex = sdata.indexOf("<",intEndIndex) + 1;
        intEndIndex = sdata.indexOf(">",intStartIndex);
        String strCommand = sdata.substring(intStartIndex,intEndIndex);

        /* Data */
        String strData = sdata.substring(intEndIndex + 1).trim();

        try {strData = new String(data,16,data.length).trim();}
        catch (Exception e) {}

        if (strDataType.equals("string")) {
            if (strCommand.equals("announce")) {arlPlayers.add(new Player(strData,2,source,port));}
            // TODO: Working here
            System.out.println("Data Received... [Type:\"" + strDataType + "\"][Command:\"" + strCommand + "\"][Data:\"" + strData + "\"]");
        }
        
        System.exit(0);
    }

    /**
     * Send data over UDP.
     *
     * Format:
     *      Header  (6 chars):          packet datatype, ex. "string","byte"
     *      Command (14 chars):         how to parse data, ex. "annouce",
     *                                  "ackannounce"
     *      Body    (1000 chars):       packet data
     *
     * Format mainly to give flexibility when determining what to do when
     * parsing data.
     *
     * Note: Chars are 1 byte
     */
    public void sendDataUDP(String type,String command,String data,String address,int port) {
        try {
            MulticastSocket socket = new MulticastSocket();
            InetAddress addr = InetAddress.getByName(address);

            if (type.length() > 6) {type.substring(0, 5);}
            if (command.length() > 14) {command.substring(0, 13);}

            type = ('<' + type + '>');
            command = ('<' + command + '>');

            while (type.length() < 8) {type += String.valueOf((char)0);}
            while (command.length() < 10) {command += String.valueOf((char)0);}
            
            byte[] bdata = new String(type + command + data).getBytes();

            DatagramPacket packet = new DatagramPacket(bdata,bdata.length,addr,port);
            socket.send(packet);
        }
        catch (Exception e) {System.out.println(e);}
    }

    /** Thread run at startup to announce player. Repeats every second until player is found */
    private class AnnounceThread extends Thread {

        private MulticastSocket ds = null;
        private static final String strAnnounce = "string   announce";
        private boolean blnAnnounce = true;
        private InetAddress group = null;
        private Timer t = new Timer();

        protected AnnounceThread()  {
            super("AnnounceThread");
            try {
                ds = new MulticastSocket();
                group = InetAddress.getByName("230.34.86.1");
            }
            catch (Exception e) {System.out.println(e);}
        }

        @Override
        public void run() {
            while (blnAnnounce) {
                try {
                    this.wait(1000);
                    DatagramPacket dp = new DatagramPacket(strAnnounce.getBytes(),strAnnounce.length(),group,54387);
                    ds.send(dp);
                }
                catch (Exception e) {System.out.println(e);}
            }
        }

        public void stopAnnounce() {blnAnnounce = false;}
    }

    /** Thread to listen for data on a TCP connection */
    private class TCPListenThread extends Thread {

        @Override
        public void run() {}
    }

    /** Thread to listen for UDP packets */
    private class UDPListenThread extends Thread {

        private MulticastSocket ds = null;
        private InetAddress group = null;
        private boolean blnListen = true;

        protected UDPListenThread(int port)  {
            super("StatusThread");
            try {
                group = InetAddress.getByName("230.34.86.1");
                ds = new MulticastSocket(port);
                ds.joinGroup(group);
            }
            catch (Exception e) {System.out.println(e);}
        }

        public void stopListening() {blnListen = false;}

        @Override
        public void run() {

            while (blnListen) {
                // Create a buffer for packet data
                byte[] buffer = new byte[1024];

                // Create a packet object to hold received packets
                DatagramPacket dp = new DatagramPacket(buffer,buffer.length);

                // Receive packet from someone
                try {ds.receive(dp);}
                catch (Exception e) {System.out.println(e);}

                /* Process data (send source address & port for UDP response) */
                receiveDataUDP(dp.getData(),dp.getAddress(),dp.getPort());
            }
            ds.close();
        }
    }
}
